package com.eppear.jpfm.framework.websocket.server;

import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.eppear.jpfm.framework.constants.Constants;
import com.eppear.jpfm.framework.exception.MyException;
import com.eppear.jpfm.framework.model.WsMessage;
import com.eppear.jpfm.framework.util.SysUtil;
import com.eppear.jpfm.framework.websocket.WebSocketServerService;
import com.eppear.jpfm.framework.websocket.WsConstants;

/**
 * @ClassName Server
 * @Description websocket通讯服务类,即于sping实现,采用了继承的方式来编写,实现了文本消息的服务
 *              配合拦截器可以实现多客户端的通讯.<br>
 *              需要注意的是:<br>
 *              1.握手成功后,需要以固定参数(wxId)方式保存会话,以便于业务层的使用.
 * @author JianFeng Zhang(张建锋)
 * @date 2017-09-27 下午8:56:37
 *
 */
public class Server extends TextWebSocketHandler {

	private static final Logger log = LoggerFactory.getLogger(Server.class);
	// ConcurrentHashMap 是 Map的线程安全类型
	private static final ConcurrentHashMap<Integer, WebSocketSession> sessions = new ConcurrentHashMap<>();
	//服务器主动发消息后,用于收消息的线程来共享回复到求请求里
	private static ConcurrentHashMap<String,WsMessage<String>> resMsg = new ConcurrentHashMap<>();
	//异步收到消息后的计数器,用于实现同步效果
	private static CountDownLatch responseCount;
	@Autowired
	WebSocketServerService webSocketServerServic;  
	/* 
	 *      与客户端完成连接后,正式通信之前调用 
     */
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		super.afterConnectionEstablished(session);
		
		Integer wxId = Integer.valueOf((String)session.getAttributes().get(WsConstants.WXID));
		if (sessions.get(wxId) == null){
			sessions.put(wxId, session);
		}
		log.debug(" 服务端 ===> 与客户端连接成功!wxId={}",wxId);
	}
	
	/* 收到客户端消息时调用 */
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		super.handleTextMessage(session, message);
		
		String reply = message.getPayload();
		//
		if("x0".equals(reply)) {
			log.debug(" 服务端 <=== 收到了客户端心跳包:[{}]",reply);
			String resp="x1";
			session.sendMessage(new TextMessage(resp));
			log.debug(" 服务端 ===> 应答了客户端心跳包:[{}]",resp);
			return;
		}
		log.info(" 服务端 <=== 收到了客户端消息:[{}]",reply);
		/*Map resMessage = (Map)JSONObject.parseObject(reply);
		String resCode = (String)resMessage.get(WsConstants.ACCESS_STR);
		*/
		WsMessage<String> reqMessage = SysUtil.jsonString2Obj(reply, WsMessage.class);
		String resCode = reqMessage.getAccStr();
		//消息为分两类,一类是客户端推送过来的,需要服务端处理,二类是服务端请求的需要客户响应,这类响应的需要去调用原来的回调方法
		if (resCode != null && resMsg.containsKey(resCode)){
			resMsg.put(resCode, reqMessage);
			responseCount.countDown();
		}else{
			webSocketServerServic.doRequest(reqMessage);
			/*WsMessage<?> resMessage =  webSocketServerServic.doRequest(reqMessage);
			if (resMessage != null &&session.isOpen()){
				session.sendMessage(new TextMessage(JSONObject.toJSONString(resMessage)));
				log.info(" 服务器 ===> 回复客户端请求消息为:[{}]",reply);
			}*/
		}
		
	}
	
	/* 连接关闭后处理
	 */
	@Override
	public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
		removeSession(session);
		super.afterConnectionClosed(session, status);
		log.debug(" 服务端 ===> 和客户端连接已断开,wxId={}",session.getAttributes().get(WsConstants.WXID));
	}
	
	@Override
	public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
		removeSession(session);
		log.error(" 服务端 ===> websocket通讯出错:{}",exception.getMessage());
		super.handleTransportError(session, exception);
	}
	
	/**
	 *  发送文本消息到客户端
	 * @param wxId 微信wxId,即表示要发到哪个业务服务器
	 * @param textMessage 消息内容,必须是文本消息
	 */
	@SuppressWarnings("rawtypes")
	public static WsMessage sendTextMessage(Integer wxId,WsMessage message){
		int ret =0;
		WebSocketSession ss;
		WsMessage rmsg = new WsMessage();
		//异步调用时的唯一码
		String resCode = UUID.randomUUID().toString();
		message.setAccStr(resCode);
		if(wxId != null && sessions.containsKey(wxId)){
			ss = sessions.get(wxId);
			if(ss !=null && ss.isOpen()){
				resMsg.put(resCode, new WsMessage<String>());
				responseCount = new CountDownLatch(1);
				try {					
					ss.sendMessage(new TextMessage(SysUtil.toJson(message)));										
				} catch (IOException e) {
					log.error("websocket发送信息失败!wxId={}",wxId);
				}
				log.info("服务端 ===> 发送消息到客户端(wxId={}):[{}]",wxId,SysUtil.toJson(message));
			}else{
				throw new MyException(Constants.ERROR_CODE_00021,"发送信息失败,业务统连接已中断");
			}
		}else{
			throw new MyException( Constants.ERROR_CODE_00020,"发送信息失败,业务统连接已中断");
		}
		try {
			responseCount.await();
			rmsg = resMsg.get(resCode);
			resMsg.remove(resCode);
		} catch (InterruptedException e) {
			log.error("websocket发送信息时等待响应消息失败!wxId={}",wxId,e);
		}
		rmsg.setResCode(ret); 
		return rmsg;
	}
	/**
	 *  删除会话
	 * @param session
	 * @throws IOException 
	 */
	private void removeSession(WebSocketSession session) throws IOException{
		Integer wxId = Integer.valueOf((String)session.getAttributes().get(WsConstants.WXID));
		if (wxId != null){
			sessions.remove(wxId);			
		}
		if (session.isOpen()) {
			session.close();
		}
	}
	
	/**
	 * 判断指定的ID是否已连接
	 * 
	 * @param id
	 * @return 参数为空时返回真，即认为是连接了。
	 */
	public static Boolean isExist(Integer id) {
		Boolean rt = false;
		if (id ==null)
			rt =true;
		else if( sessions.get(id) != null)
			rt =true;
		
		return rt;
	}
	
}
